package com.rou.im.framework.core.log;
import java.io.File;
import java.io.IOException;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * <p>
 *
 *       <b>日志管理类。</b>定时检查被监测日志目录使用的总磁盘空间，当使用的总磁盘空间超过设定值时，<br>
 *  * 根据日志文件最后修改时间，删除最老的日志文件，直至日志目录使用的磁盘空间小于设定的值；<br>
 *  * 定时检测日志文件的最后修改时间，当日志文件的保留时间超过设定值时，删除这些日志文件。
 * </p>
 *
 * @author GaoShuangWen
 * @since 2020-05-13 22:49
 */

public class DLoggerChecker extends Thread {

    private static DLoggerChecker manager = null;
    // private CheckTask task = new CheckTask(); //与计时器配合工作的任务
    // private Timer checkTimer = new Timer(); //计时器

    private List<File> listTempleFile = new ArrayList<File>(); // 临时保存待处理的日志文件列表
    private Long totalSize = 0L; // 临时保存日志文件使用的总的磁盘空间大小
    /**
     * 需要监控的日志文件目录
     */
    private ArrayList<File> logPaths = new ArrayList<File>();
    /**
     * 对日志文件进行定时检查的时间间隔，单位：毫秒
     */
    private int logCheckInterval = 60000;

    /**
     * 日志文件size的最大值，单位：千字节
     */
    private long totalLogSizeLimit = 2*1024 * 1024 ;

    /**
     * 日志文件需要保留的最长时间，单位：天
     */
    private int maxLogFileKeepDays = 10;

    /**
     * 日志文件需要保留的最长时间，单位：ms
     */
    private int maxLogFileKeeptime = 360000000;

    /**
     * 文件按照最后修改时间进行排序的比较器
     */
    private CompareFile cmpFile = new CompareFile();

    public int getLogCheckInterval() {
        return logCheckInterval;
    }

    /**
     * 设置定时监测日志目录的时间间隔，单位：毫秒
     *
     * @param logCheckInterval
     */
    public void setLogCheckInterval(int logCheckInterval) {
        this.logCheckInterval = logCheckInterval;
    }

    public long getTotalLogSizeLimit() {
        return totalLogSizeLimit;
    }

    /**
     * 设置被监测日志文件可使用的最大磁盘空间，单位：字节
     *
     * @param maxLogFileSize
     */
    public void setTotalLogSizeLimit(long maxLogFileSize) {
        this.totalLogSizeLimit = maxLogFileSize;
    }

    public int getMaxLogFileKeepDays() {
        return maxLogFileKeepDays;
    }

    /**
     * 设置日志文件可保留的天数，单位：天
     *
     * @param maxLogFileKeepDays
     */
    public void setMaxLogFileKeepDays(int maxLogFileKeepDays) {
        this.maxLogFileKeepDays = maxLogFileKeepDays;
        this.maxLogFileKeeptime = this.maxLogFileKeepDays * 24 * 60 * 60 * 1000;
    }

    public ArrayList<File> getLogPaths() {
        return logPaths;
    }

    /**
     * 增加被监测的日志目录
     *
     * @param dir
     */
    public void addLogPaths(String dir) {
        this.logPaths.add(new File(dir));
    }

    /**
     * 增加被监测的日志目录
     *
     * @param
     */
    public void addAllLogPaths(Collection<String> paths) {
        for (String path : paths) {
            this.logPaths.add(new File(path));
        }
    }

    private DLoggerChecker() {

    }

    public static DLoggerChecker getInstance() {
        if (manager == null) {
            manager = new DLoggerChecker();
            manager.setName("DLoggerChecker");
        }
        return manager;
    }

    /*
     * public void start() { checkTimer.schedule(task, logCheckInterval,
     * logCheckInterval); }
     */
    /*
     * public void stop() { task.cancel(); task = null; }
     */

    /**
     * 计算所有被监控目录的大小
     *
     * @param dir
     *            目录名
     */
    private void getTotalSize(File dir) {
        if (dir.isDirectory()) {
            if (dir.listFiles() == null) {
                return;
            }
            for (File file : dir.listFiles()) {
                getTotalSize(file);
            }
        } else {
            totalSize += dir.length();
        }
    }

    /**
     * 按文件最后修改的先后顺序删除文件，直到目录的大小小于配置中设定的最大值为止
     */
    private void deleteEarliestFile() {
        try {
            if (listTempleFile.isEmpty())
                return;
            Collections.sort(listTempleFile, cmpFile); // 文件列表按照最后修改时间从前到后排序

            while (totalSize/1024 > totalLogSizeLimit) {
                if (listTempleFile.isEmpty() || listTempleFile.size() == 1)
                    return;
                File file = listTempleFile.remove(0);
                long fileLength = file.length();
                if (file.delete()) {
                    totalSize -= fileLength;
                } else {
                    LOG.error("[log]cannot delete file " + file.getName() + " may be opened\r\n");
                }
            }
        } catch (Exception e) {
            LOG.error("[log]DLoggerChecker Exception \r\n" + "[Exception]" + e.getMessage());
        }
    }

    /**
     * 删除最后修改时间早于配置中需要保留天数之前的文件,目录延后一个监测周期删除
     *
     * @param now
     *            比较基点，早于该时间的文件需要删除
     * @param file
     *            检测的目录名或文件名
     */
    private void deleteOldFiles(Calendar now, File file) {
        if (file == null || !file.exists())
            return;
        Calendar calendar = Calendar.getInstance();
        if (file.isDirectory()) {
            calendar.setTimeInMillis(file.lastModified());
            File[] flst = file.listFiles();
            if (file.listFiles() == null) {
                return;
            }
            if (flst.length > 0) {
                for (File f : flst) {
                    deleteOldFiles(now, f);
                }

                flst = file.listFiles();
                if (flst.length == 0) {
                    // 删除过期的空目录
                    if (calendar.before(now)) {
                        try {
                            boolean  delete=	file.delete();
                        } catch (Exception e) {
                            LOG.error(e.toString());
                        }
                    }
                }
            } else {
                // 删除过期的空目录
                if (calendar.before(now)) {
                    try {
                        boolean  delete=	file.delete();

                    } catch (Exception e) {
                        LOG.error(e.toString());
                    }
                }
            }

        } else {
            if (file.exists()) {
                calendar.setTimeInMillis(file.lastModified());
                if (calendar.before(now)) {
                    try {
                        boolean  delete=	file.delete();
                    } catch (Exception e) {
                        LOG.error(e.toString());
                    }
                }
            }
        }

    }

    /**
     * 递归获取给定目录中的文件，并保存到成员变量fileLst中
     *
     * @param file
     */
    private void getFilelist(File file) {
        if (file == null || !file.exists())
            return;
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                getFilelist(f);
            }
        } else {
            if (file.exists()) {
                listTempleFile.add(file);
            }
        }

    }

    static {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                if (t instanceof JLogBase) {
                    LOG.error("FAT: Thread ", JLogBase.class.getName(), " died due to ", E.formatThrowable(e));
                }
            }
        });

        /**
         * this is to avoid the jvm bug: NullPointerException in Selector.open()
         * http://bugs.sun.com/view_bug.do?bug_id=6427854
         */
        try {
            Selector.open().close();
        } catch (IOException e) {
            LOG.error("Selector failed to open", E.formatException(e));
        }
    }

    @Override
    public void start() {
        // ensure thread is started once and only once
        if (getState() == Thread.State.NEW) {
            setDaemon(true); // 确保程序退出时，虚拟机会终止daemon线程
            super.start();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                long startTime = System.currentTimeMillis();
                Calendar now = Calendar.getInstance();
                now.setTimeInMillis(now.getTimeInMillis() - maxLogFileKeeptime);
                totalSize = 0L;
                for (File logPath : logPaths) {
                    if (!logPath.exists()) {
                        LOG.warn(logPath.getAbsolutePath() + " not exist");
                        continue;
                    }
                    if (!logPath.isDirectory()) {
                        LOG.warn(logPath.getAbsolutePath() + " isn't a Directory");
                        continue;
                    }
                    deleteOldFiles(now, logPath);
                    getTotalSize(logPath);
                }
                // 目录总和大于规定大小
                // 找到所有目录中最早的一天的目录（可以有多个）
                // 删除目录中最早一小时(00-23)的文件直到totalSize < 规定值
                // 如果删除后目录为空，移除目录
                // 每次删除100个文件，防止删除失败时，无限尝试
                if (totalSize/1024 > totalLogSizeLimit) {
                    listTempleFile.clear();
                    for (File logPath : logPaths) {
                        getFilelist(logPath);
                    }
                    deleteEarliestFile();
                }
                long timeSplash = logCheckInterval - (System.currentTimeMillis() - startTime);
                if (timeSplash > 0) {
                    try {
                        Thread.sleep(timeSplash);
                    } catch (InterruptedException e) {
                        LOG.error(LOG.getTrace(e));
                    }
                }
            } catch (Throwable t) {
                LOG.error("[LOG CHECK]" + E.formatThrowable(t));
                LOG.error(LOG.getTrace(t));
            }
        }
    }

    /**
     * 比较两个文件的最后修改时间，按从前到后的顺序排列。
     *
     * @author ampthon
     *
     */
    class CompareFile implements Comparator<File> {

        @Override
        public int compare(File arg0, File arg1) {
            // TODO Auto-generated method stub
            long L0 = arg0.lastModified();
            long L1 = arg1.lastModified();
            if (L0 > L1)
                return 1;
            else if (L0 == L1)
                return 0;
            else
                return -1;
        }
    }

    public void startup() {

    }
}
